package quickstats;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.text.DecimalFormat;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

/**
 * This is used to generate a scatter plot graph.  
 * Graph always shows 5 divisions.
 * Graph's precision is only as good as the nearest integer.
 * Graph's number labels are prone to overlapping if numbers are very high.
 * 
 */

public class QuickStatsGrapher extends JPanel {
	
	private static final long serialVersionUID = 1L;
	
	//input data sets
	protected double[] xPoints;
	protected double[] yPoints;
	
	//adjusts spacing of graph if number labels overlap
	protected int xAdjust = 1;
	protected int yAdjust = 1;
	protected double onlyXAdjust = 0;
	
	//used to label graph division points
	protected double xMin;
	protected double xMax;
	protected double yMin;
	protected double yMax;
	protected double xRange;
	protected double yRange;
	
	//coordinates used to draw x-axis
	protected int x1 = 50;
	protected int y1 = 350;
	protected int x2 = 600;
	protected int y2 = 350;
	
	//coordinates used to draw y-axis
	protected int x3 = 50;
	protected int y3 = 50;
	protected int x4 = 50;
	protected int y4 = 350;
	
	/*
	 * Constructor
	 */
	QuickStatsGrapher(double[] sampleOne, double[] sampleTwo) {
		xPoints = sampleOne;
		yPoints = sampleTwo;
		
		computeMin();
		computeMax();
		
		//resizing of graph if values over 1 million exist
		if (xMax >= 1000000) xAdjust = 2;
		if (yMax >= 1000000) yAdjust = 2;
		if (xMin <= -1000000) xAdjust = 2;
		if (yMin <= -1000000) yAdjust = 2;
		
		//adjusts coordinates used to draw x-axis
		x1 = 50 * yAdjust;
		y1 = 350;
		x2 = 600 * xAdjust;
		y2 = 350;
		
		//adjusts coordinates used to draw y-axis
		x3 = 50 * yAdjust;
		y3 = 50;
		x4 = 50 * yAdjust;
		y4 = 350;
		
		//used to adjust x-coordinate when only y-coordinate is modified and pushes everything right
		if (yAdjust == 2 && xAdjust == 1) x2 += 50;
		
		//used for doubling x-coordinate plane length without modifying y-coordinate
		if (yAdjust == 1 && xAdjust == 2) onlyXAdjust = xAdjust * 0.045;
	}

	/*
	 * draws graph using graphics class
	 * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
	 */
	public void paintComponent(Graphics g) {
		Graphics2D graph = (Graphics2D) g;
		graph.drawString("Scatter Plot", (x2 - x1) / 2, 40);
		
		//reize of font if over max values over 1000
		if ((xMax >= 1000 || yMax >= 1000) || (xMin <= -1000 || yMin <= -1000)) {
			Font font = new Font("Arial", Font.PLAIN, 9);
			graph.setFont(font);
		}
		
		drawXCoordinate(graph);
		drawYCoordinate(graph);
		plot(graph);
		computeLinearFit(graph);
	}
	
	/*
	 * Used to draw and label x-coordinate plane
	 */
	public void drawXCoordinate(Graphics g) {
		g.drawLine(x1, y1, x2, y2); //draws x-coordinate

		for (int i = 1; i <= 10; i++) {
			g.drawLine((int) (x1 + (50 * i * (xAdjust + onlyXAdjust))), y1 - 2, (int) (x1 + (50 * i * (xAdjust + onlyXAdjust))), y2 + 2); //draws x-coordinate line
			//numbers x-coordinate divisions
			if (xMin != xMax) {
				double increment = xMin + (((xMax - xMin) / 9) * (i - 1));
				g.drawString(roundDouble(increment) + "", (int) (x1 + (50 * i * (xAdjust + onlyXAdjust)) - 7), y1 + 15);
			} else if (xMin == xMax && i == 1) {
				g.drawString(roundDouble(xMax) + "", (int) (x1 + (50 * i * (xAdjust + onlyXAdjust)) - 7), y1 + 15);
			}
		}
	}
	
	/*
	 * Used to draw and label y-coordinate plane
	 */
	public void drawYCoordinate(Graphics g) {
		g.drawLine(x3, y3, x4, y4);  //draws y-coordinate
		for (int i = 1; i <= 5; i++) {
			g.drawLine(x3 - 2, y4 - (50 * i), x3 + 2, y4 - (50 * i));
			
			//numbers y-coordinate divisions
			if (yMin != yMax) {
				double increment = roundDouble(yMin + (((yMax - yMin) / 4) * (i - 1)));
				g.drawString(roundDouble(increment) + "", x3 - (45 * yAdjust), y4 - (50 * i) + 5);
			} else if (yMin == yMax && i == 1) {
				g.drawString(roundDouble(yMax) + "", x3 - (45 * yAdjust), y4 - (50 * i) + 5);
			}
		}
	}
	
	/*
	 * plots data points to the graph with the input parameter g being the graph to draw on
	 */
	public void plot(Graphics g) {
		
		//pixel range that is used to calculate correct pixel to value ratio for output
		int x_pixelRange = x2 - x1 - (int) (100 * (xAdjust + onlyXAdjust));
		int y_pixelRange = y4 - y3 - 100;
		//actual range of data to properly convert pixel to value ratio for output
		double x_valueRange = xMax - xMin;
		double y_valueRange = yMax - yMin;
		
		//checks for data sets with no variance
		if (xMin == xMax) {
			x_valueRange = xMax;
		}
		if (yMin == yMax) {
			y_valueRange = yMax;
		}
		
		//gives single pixel to value ratio estimation to fit data to screen correctly
		double x_Ratio = (x_pixelRange / x_valueRange);
		double y_Ratio = (y_pixelRange / y_valueRange);
		
		//used to properly place positive and negative values due to value reversal and range increase by negative numbers
		double x_negativePixelRange = Math.abs(xMin) * x_Ratio;
		double y_negativePixelRange = Math.abs(yMin) * y_Ratio;

		//used to temporarily store values to be plotted 
		double tempX = 0;
		double tempY = 0;

		//calculates and plots points
		for (int i = 0; i < xPoints.length; i++) {
			if (xMin <= 0) {
				if (xPoints[i] == 0) {
					tempX = (x_negativePixelRange) + x1 + (50 * (xAdjust + onlyXAdjust)); //places negative point
				} else if (xPoints[i] < 0) {
					tempX = (x_negativePixelRange - (Math.abs(xPoints[i]) * x_Ratio)) + x1 + (50 * (xAdjust + onlyXAdjust)); //places negative point
				} else {
					tempX = (x_negativePixelRange + (Math.abs(xPoints[i]) * x_Ratio)) + x1 + (50 * (xAdjust + onlyXAdjust)); //places positive point
				}
			} else if (xMin > 0) {
				tempX = (xPoints[i] * x_Ratio) + x1 + (50 * (xAdjust + onlyXAdjust)) - (xMin * x_Ratio); //only positive points on graph
			}
			
			if (yMin <= 0) {
				if (yPoints[i] == 0) {
					tempY = y4 - (y_negativePixelRange) - 50; //places negative point
				} else if (yPoints[i] < 0) {
					tempY = y4 - (y_negativePixelRange - (Math.abs(yPoints[i]) * y_Ratio)) - 50;
				} else {
					tempY = y4 - (y_negativePixelRange + ((Math.abs(yPoints[i]) * y_Ratio))) - 50;
				}
			} else if (yMin > 0) {
				tempY = y4 - (yPoints[i] * y_Ratio) - 50 + (yMin * y_Ratio); //used for only positive points on graph
			}
			g.drawOval((int) tempX, (int) tempY, 4, 4);
		}
	}
	
	/* computes linear fit of graph points using y = mx + b */
	public void computeLinearFit(Graphics g) {
		int nSize = xPoints.length;
		double x_sum = computeSum(xPoints);
		double y_sum = computeSum(yPoints);
		double xy_sum = computeSumOfProductXY();
		double xSquare = computeSumOfXSquare();
		double ySquare = computeSumOfYSquare();
		
		double m = computeSlope(nSize, x_sum, y_sum, xy_sum, xSquare);
		double b = computeY_Intercept(nSize, x_sum, y_sum, xy_sum, xSquare);
		
		double linear_x1 = (yMin - b) / m;
		double linear_x2 = (yMax - b) / m;
		double linear_y1 = m * (xMin) + b;
		double linear_y2 = m * (xMax) + b;
		
		drawLinearRegressionLine(g, linear_x1, linear_y1, linear_x2, linear_y2);
	}
	
	public void drawLinearRegressionLine(Graphics g, double linear_x1 , double linear_y1, double linear_x2, double linear_y2) {
		double[] xLinePlots = {linear_x1, linear_x2};
		double[] yLinePlots = {linear_y1, linear_y2};
				
		//pixel range that is used to calculate correct pixel to value ratio for output
		int x_pixelRange = x2 - x1 - (int) (100 * (xAdjust + onlyXAdjust));
		int y_pixelRange = y4 - y3 - 100;
		//actual range of data to properly convert pixel to value ratio for output
		double x_valueRange = xMax - xMin;
		double y_valueRange = yMax - yMin;
		
		//checks for data sets with no variance
		if (xMin == xMax) {
			x_valueRange = xMax;
		}
		if (yMin == yMax) {
			y_valueRange = yMax;
		}
		
		double x_Ratio = (x_pixelRange / x_valueRange); //gives single pixel to value estimation to fit data to screen correctly
		double y_Ratio = (y_pixelRange / y_valueRange);
		
		//used to properly place positive and negative values due to value reversal and range increase by negative numbers
		double x_negativePixelRange = Math.abs(xMin) * x_Ratio;
		double y_negativePixelRange = Math.abs(yMin) * y_Ratio;

		//used to temporarily store values to be used to draw line 
		double tempX1 = 0;
		double tempY1 = 0;
		double tempX2 = 0;
		double tempY2 = 0;
		
		//calculates and plots points
		for (int i = 0; i < xLinePlots.length; i++) {
			if (xMin <= 0) {
				if (xLinePlots[i] == 0) {
					tempX2 = (x_negativePixelRange) + x1 + (50 * (xAdjust + onlyXAdjust)); //places negative point
				} else if (xLinePlots[i] < 0) {
					tempX2 = (x_negativePixelRange - (Math.abs(xLinePlots[i]) * x_Ratio)) + x1 + (50 * (xAdjust + onlyXAdjust)); //places negative point
				} else {
					tempX2 = (x_negativePixelRange + (Math.abs(xLinePlots[i]) * x_Ratio)) + x1 + (50 * (xAdjust + onlyXAdjust)); //places positive point
				}
			} else if (xMin > 0) {
				tempX2 = (xLinePlots[i] * x_Ratio) + x1 + (50 * (xAdjust + onlyXAdjust)) - (xMin * x_Ratio); //only positive points on graph
			}
			
			if (yMin <= 0) {
				if (yLinePlots[i] == 0) {
					tempY2 = y4 - (y_negativePixelRange) - 50; //places negative point
				} else if (yLinePlots[i] < 0) {
					tempY2 = y4 - (y_negativePixelRange - (Math.abs(yLinePlots[i]) * y_Ratio)) - 50;
				} else {
					tempY2 = y4 - (y_negativePixelRange + ((Math.abs(yLinePlots[i]) * y_Ratio))) - 50;
				}
			} else if (yMin > 0) {
				tempY2 = y4 - (yLinePlots[i] * y_Ratio) - 50 + (yMin * y_Ratio); //used for only positive points on graph
			}
			
			//copies first two coordinates to appropriate variables tempX1 and tempY1
			if (i == 0) {
				tempX1 = tempX2;
				tempY1 = tempY2;
			}
		}
		g.drawLine((int) tempX1, (int) tempY1, (int) tempX2, (int) tempY2);
	}
	
	/* computes slope of a line */
	public double computeSlope(int n, double x, double y, double xy, double xx) {
		double m = ((n * xy) - (x * y)) / ((n * xx) - (x * x));
		return m;
	}
	
	/* computes y-intercept of a line */
	public double computeY_Intercept(int n, double x, double y, double xy, double xx) {
		double b = ((xx * y) - (x * xy)) / ((n * xx) - (x * x));
		return b;
	}
	
	/* used for linear fit */
	public double computeSumOfProductXY() {
		double xy = 0;
		for (int i = 0 ; i < xPoints.length; i++) {
			xy += xPoints[i] * yPoints[i];
		}
		
		return xy;
	}
	
	/* used for linear fit */
	public double computeSumOfXSquare() {
		double xSquare = 0;
		for (int i = 0; i < xPoints.length; i++) {
			xSquare += Math.pow(xPoints[i], 2);
		}
		
		return xSquare;
	}
	
	/* used for linear fit */
	public double computeSumOfYSquare() {
		double ySquare = 0;
		for (int i = 0; i < yPoints.length; i++) {
			ySquare += Math.pow(yPoints[i], 2);
		}
		
		return ySquare;
	}
	
    /*
     * computeMax
     * computes the greatest element of the sample
     * this is used to compute max values for x and y coordinates
     */
    public void computeMax() {
    	
    	try {
    		double tempMax = xPoints[0];
    		double tempMax2 = yPoints[0];
    		
            for (int i = 0; i < xPoints.length; i++) {
                if (xPoints[i] > tempMax) {
                    tempMax = xPoints[i];
                }
            }
            xMax = tempMax;
            
            for (int i = 0; i < yPoints.length; i++) {
                if (yPoints[i] > tempMax2) {
                    tempMax2 = yPoints[i];
                }
            }
            yMax = tempMax2;
            
    	} catch (NullPointerException error) {
    		JFrame frame = new JFrame("Error");
    		JOptionPane.showMessageDialog(frame, "Graphing error has occured.");
    	}     
    }
    
    /*
     * computeMin
     * computes the smallest element of the sample
     * this is used to compute the min values for the x and y coordinates
     */
    public void computeMin() {
    	
    	try {
    		double tempMin = xPoints[0];
    		double tempMin2 = yPoints[0];
    		
            for (int i = 0; i < xPoints.length; i++) {
                if (xPoints[i] < tempMin) {
                    tempMin = xPoints[i];
                }
            }

            xMin = tempMin;
            
            for (int i = 0; i < yPoints.length; i++) {
                if (yPoints[i] < tempMin2) {
                    tempMin2 = yPoints[i];
                }
            }

            yMin = tempMin2;
            
    	} catch (NullPointerException error) {
    		JFrame frame = new JFrame("Error");
    		JOptionPane.showMessageDialog(frame, "Graphing error has occured.");
    	}
    }
	
	/*
	 * Print graph function.
	 * This is the only method that should be called after making object
	 */
	public void printGraph() {	
		JFrame frame = new JFrame("Bivariate Analysis: Linear Regression Scatterplot");
		//frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //closes entire program when graph is closed

		frame.setSize((x2 * xAdjust) + 100, y4 + 100);
		frame.setVisible(true);
    
		frame.getContentPane().add(this);
	}
	
	/*
	 * Rounds graph number labels to two decimal places
	 */
	public double roundDouble(double d) {
		DecimalFormat twoDForm = new DecimalFormat("#.##");
		return Double.valueOf(twoDForm.format(d));
	}
	
    /*
     * computeSum
     * computes the total of the sample
     * used for linear regression computation
     */
    public double computeSum(double[] data) {
        double temp = 0;

        for (int i = 0; i < data.length; i++) {
            temp += data[i];
        }

        return temp;
    }
}